import { INodeInputSlot, INodeOutputSlot, LGraphNode, LiteGraph, LLink } from "litegraph.js";
import { cloneDeep } from "lodash";
import { LitegraphUtils } from "../../utils/LitegraphUtils";
import { CpcDrawerCtrl } from "../../../../components/CpcDrawer/CpcDrawerCtrl";

export type ICpcBaseIOType = "String" | "Number" | "Boolean" | "Object" | "Array" | "ArrowFlow" | number;

export interface ICpcBaseIO {
    name: string;
    type: ICpcBaseIOType;
    label?: string;
}

export interface ICpcBaseIos {
    disablePreInput?: boolean;
    disableNextOutput?: boolean;
    inputs: ICpcBaseIO[];
    outputs: ICpcBaseIO[];
}

const TypeMapShapeColor: any = {
    String: {
        shape: LiteGraph.CIRCLE_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#aaa",
    },
    Number: {
        shape: LiteGraph.CIRCLE_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#34ad19",
    },
    Boolean: {
        shape: LiteGraph.CIRCLE_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#ab0909",
    },
    Object: {
        shape: LiteGraph.BOX_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#1448ad",
    },
    Array: {
        shape: LiteGraph.BOX_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#9a3434",
    },
    ArrowFlow: {
        shape: LiteGraph.ARROW_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#666",
        // eslint-disable-next-line camelcase
        color_on: "#ff0",
    },
    0: {
        shape: LiteGraph.BOX_SHAPE,
        // eslint-disable-next-line camelcase
        color_off: "#aaa",
    },
};

function setTypeMapShapeColor(item: any): any {
    if (!item.label) {
        item.label = item.name;
    }
    const typeMapResult = TypeMapShapeColor[item.type];
    Object.keys(typeMapResult).forEach(key => {
        item[key] = typeMapResult[key];
    });
    return item;
}

export abstract class CpcBase extends LGraphNode {
    constructor(ios: ICpcBaseIos, title?: string) {
        super(title);
        this.buildTheme(ios);
    }

    protected codeEditDbShow: boolean = true; // 是否响应双击打开代码编辑窗口

    protected codeEidtReadOnly: boolean = false; // 打开代码编辑窗口后是否允许编辑

    // 构建统一样式
    private buildTheme(ios: ICpcBaseIos) {
        // 定义流程线函数
        const ios2 = cloneDeep(ios);
        if (!ios2.disablePreInput) {
            ios2.inputs.unshift({
                name: "pre",
                type: "ArrowFlow",
            });
        }
        if (!ios2.disableNextOutput) {
            ios2.outputs.unshift({
                name: "next",
                type: "ArrowFlow",
            });
        }
        this.inputs = ios2.inputs.map(item => setTypeMapShapeColor(item));
        this.outputs = ios2.outputs.map(item => setTypeMapShapeColor(item));
    }

    /**
     * 运行代码逻辑，要支持服务端运行
     * @param preData 输入的所有数据，按节点取
     * @param preData 输出的节点信息
     * @returns 输出给下一个组件的数据，按节点给
     */
    public abstract cpcRunCode(preDatas: any[], inputInfos: any[], outputInfos: any[]): any[];

    private isConfigureIng = false;

    public configure(info: any) {
        this.isConfigureIng = true;
        super.configure(info);
        this.setCpcRunCodeStr(info.cpcRunCode);
        this.isConfigureIng = false;
    }

    public serialize() {
        const reulst: any = super.serialize();
        reulst.cpcRunCode = this.getCpcRunCodeStr();
        return reulst;
    }

    public onDblClick() {
        if (!this.codeEditDbShow) {
            return;
        }
        const self = this;
        const detail = this.getInputMockDataDetailsNot0();
        CpcDrawerCtrl.open(
            {
                component: "CodeEdit",
                callBack(data: any, close: any) {
                    self.setCpcRunCodeStr(data);
                    close();
                },
                data: {
                    code: this.getCpcRunCodeStr(),
                    parame: {
                        preDatas: detail.datas,
                        inputInfo: detail.inputInfos,
                        outputInfos: this.getOutputInfosNot0(),
                    },
                    readOnly: this.codeEidtReadOnly,
                },
            },
            { title: "代码编辑", styleType: "full" }
        );
    }

    // 负责脱离方法体
    private getCpcRunCodeStr(): string {
        let result = this.cpcRunCode.toString();
        const sI = result.indexOf("{") + 1;
        const eI = result.lastIndexOf("}");
        result = result.substring(sI, eI);
        return result;
    }

    // 负责包装方法体
    private setCpcRunCodeStr(data: string) {
        try {
            // @ts-ignore
            this.cpcRunCode = new Function("preDatas", "inputInfos", "outputInfos", data);
        } catch (e) {
            console.error("运行函数出错了");
        }
    }

    public onConnectInput(inputIndex: number, outputType: INodeOutputSlot["type"], outputSlot: INodeOutputSlot, outputNode: LGraphNode, outputIndex: number): boolean {
        // 判断是否是流程线
        if (outputType === "ArrowFlow" && inputIndex === 0) {
            // 判断输入节点是否已经连接过了
            if (outputSlot.links && outputSlot.links.length > 0) {
                // 断开已经连接的，连接到当前的
                if (outputNode.disconnectOutput(0)) {
                    // @ts-ignore
                    outputNode.connect(0, this, 0);
                }
                return false;
            }
        }
        return true;
    }

    public onConnectionsChange(type: number, slotIndex: number, isConnected: boolean, link: LLink, ioSlot: INodeOutputSlot | INodeInputSlot) {
        if (this.isConfigureIng) {
            return;
        }
        // type:1--输出，2--输入;   isConnected:true--连接，false---断开
        if (type === 1) {
            if (isConnected) {
                this.onCpcConnectInput(slotIndex, link, ioSlot);
            } else {
                this.onCpcDisConnectInput(slotIndex, link, ioSlot);
            }
        } else if (isConnected) {
            this.onCpcConnectOutput(slotIndex, link, ioSlot);
        } else {
            this.onCpcDisConnectOutput(slotIndex, link, ioSlot);
        }
    }

    // 根据需求重写即可
    public onCpcConnectInput(slotIndex: number, link: LLink, ioSlot: INodeOutputSlot | INodeInputSlot) {}

    // 根据需求重写即可
    public onCpcDisConnectInput(slotIndex: number, link: LLink, ioSlot: INodeOutputSlot | INodeInputSlot) {}

    // 根据需求重写即可
    public onCpcConnectOutput(slotIndex: number, link: LLink, ioSlot: INodeOutputSlot | INodeInputSlot) {}

    // 根据需求重写即可
    public onCpcDisConnectOutput(slotIndex: number, link: LLink, ioSlot: INodeOutputSlot | INodeInputSlot) {}

    public removeInputNot0(slot: number) {
        this.removeInput(slot + 1);
    }

    public removeInputsNot0() {
        for (let i = this.inputs.length - 1; i >= 1; i--) {
            this.removeInput(i);
        }
    }

    public removeOutputNot0(slot: number) {
        this.removeOutput(slot + 1);
    }

    public removeOutputsNot0() {
        for (let i = this.outputs.length - 1; i >= 1; i--) {
            this.removeOutput(i);
        }
    }

    public addInput(name: string, type: string | -1, extraInfo?: Partial<INodeInputSlot>): INodeInputSlot {
        extraInfo = setTypeMapShapeColor({ name, type });
        return super.addInput(name, type, extraInfo);
    }

    public addOutput(name: string, type: string | -1, extraInfo?: Partial<INodeOutputSlot>): INodeOutputSlot {
        extraInfo = setTypeMapShapeColor({ name, type });
        return super.addOutput(name, type, extraInfo);
    }

    public getInputsNot0() {
        return this.inputs.filter((item, index) => {
            return index > 0;
        });
    }

    public getOutputsNot0() {
        return this.outputs.filter((item, index) => {
            return index > 0;
        });
    }

    public getInputDataNot0<T = any>(slot: number, forceUpdate?: boolean): T {
        return this.getInputData(slot + 1, forceUpdate);
    }

    // 获取所有节点的数据
    public getInputDatasNot0() {
        const result = [];
        for (let i = 1; i < this.inputs.length; i++) {
            result.push(this.getInputData(i));
        }
        return result;
    }

    public setOutputDataNot0(slot: number, data: any) {
        this.setOutputData(slot + 1, data);
    }

    public _setOutputDatasNot0(arr: any) {
        arr.forEach((item: any, index: number) => {
            this.setOutputDataNot0(index, item);
        });
    }

    public getOutputDataNot0<T = any>(slot: number): T | null {
        return this.getOutputData(slot + 1);
    }

    public getOutputInfoNot0(slot: number): { name: string; type: string; links: number[] } | null {
        return this.getOutputInfo(slot + 1);
    }

    public getOutputInfosNot0() {
        const result = [];
        for (let i = 1; i < this.outputs.length; i++) {
            result.push(this.getOutputInfo(i));
        }
        return result;
    }

    public getInputInfoNot0(slot: number): { link: number; name: string; type: string | 0 } | null {
        return this.getInputInfo(slot + 1);
    }

    public getInputInfosNot0() {
        const result = [];
        for (let i = 1; i < this.inputs.length; i++) {
            result.push(this.getInputInfo(i));
        }
        return result;
    }

    public getOutputNodesNot0(slot: number): LGraphNode[] {
        return this.getOutputNodes(slot + 1);
    }

    public getInputNodeNot0(slot: number): LGraphNode | null {
        return this.getInputNode(slot + 1);
    }

    /**
     * 获取输出节点的详情，同时获取连接节点
     * @param slot
     */
    public getOutputNodesDetail(slot: number): Array<{ node: any; links: number[] }> {
        const info = this.getOutputInfo(slot);
        const nodes = this.getOutputNodes(slot);
        if (!info || !nodes) {
            return [];
        }
        const result = nodes.map(node => {
            const links: number[] = [];
            node.inputs.forEach((input, index) => {
                if (!input.link || info.links.indexOf(input.link) === -1) {
                    return;
                }
                links.push(index);
            });
            return {
                node,
                links,
            };
        });
        return result;
    }

    /**
     * 获取输入节点的详情，同时获取连接节点
     * @param slot
     */
    public getintputNodesDetail(slot: number): null | { node: any; link: number } {
        const info = this.getInputInfo(slot);
        const node = this.getInputNode(slot);
        if (info && node) {
            for (let index = 0; index < node.outputs.length; index++) {
                const output = node.outputs[index];
                if (output.links && output.links.length > 0) {
                    if (output.links.indexOf(info.link) !== -1) {
                        return { node, link: index };
                    }
                }
            }
        }
        return null;
    }

    public connectNot0<T = any>(slot: number | string, targetNode: LGraphNode, targetSlot: number | string): T | null {
        if (typeof slot === "number") {
            slot += 1;
        }
        if (typeof targetSlot === "number") {
            targetSlot += 1;
        }
        return this.connect(slot, targetNode, targetSlot);
    }

    public disconnectOutputNot0(slot: number | string, targetNode?: LGraphNode): boolean {
        if (typeof slot === "number") {
            slot += 1;
        }
        return this.disconnectOutput(slot, targetNode);
    }

    public disconnectInputNot0(slot: number | string): boolean {
        if (typeof slot === "number") {
            slot += 1;
        }
        return this.disconnectInput(slot);
    }

    public isInputConnectedNot0(slot: number): boolean {
        return this.isInputConnected(slot + 1);
    }

    // 更新输入节点
    public updateInputsNot0(arr: Array<{ name: string; type: string | -1 }>) {
        const inputs = this.inputs;
        const linkMap: any = {};
        const len = inputs.length;
        for (let i = len - 1; i >= 1; i--) {
            const item = inputs[i];
            const index = LitegraphUtils.findIndex(arr, item, ["name", "type"]);
            if (index !== -1) {
                linkMap[item.name] = this.getintputNodesDetail(i);
            }
            this.removeInput(i);
        }
        arr.forEach((item: any, index: number) => {
            this.addInput(item.name, item.type);
            const nodesDetail = linkMap[item.name];
            if (nodesDetail) {
                const node = nodesDetail.node;
                node.connect(nodesDetail.link, this, index + 1);
            }
        });
    }

    // 更新输出节点
    public updateOutputsNot0(arr: Array<{ name: string; type: string | -1 }>) {
        const outputs = this.outputs;
        const linkMap: any = {};
        const len = outputs.length;
        for (let i = len - 1; i >= 1; i--) {
            const item = outputs[i];
            const index = LitegraphUtils.findIndex(arr, item, ["name", "type"]);
            if (index !== -1) {
                linkMap[item.name] = this.getOutputNodesDetail(i);
            }
            this.removeOutput(i);
        }
        arr.forEach((item: any, index: number) => {
            this.addOutput(item.name, item.type);
            const nodesDetails = linkMap[item.name];
            if (nodesDetails && nodesDetails.length > 0) {
                nodesDetails.forEach((nodesDetail: any) => {
                    const node = nodesDetail.node;
                    nodesDetail.links.forEach((link: number) => {
                        this.connect(index + 1, node, link);
                    });
                });
            }
        });
    }

    public getInputDataDetailsNot0() {
        const inputInfos = [];
        const datas = [];
        for (let i = 1; i < this.inputs.length; i++) {
            const info = this.getInputInfo(i);
            inputInfos.push({
                type: info?.type, // 字段类型
                name: info?.name, // 字段名称
                data: this.getInputData(i), // 数据
            });
            datas.push(this.getInputData(i));
        }
        return { inputInfos, datas };
    }

    // start模拟
    public onStart() {
        // 没有输入输出，不执行
        if (!this.isInputConnected(0) && !this.isOutputConnected(0)) {
            return;
        }
        const result = this.cpcRun();
        this._setOutputDatasNot0(result);
    }

    // 原理就是去执行流程的上一个节点的cpcRun，直到尽头
    public cpcRun(isMock = false): any[] {
        let inputInfos: any[] = [];
        let datas: any[] = [];
        if (this.properties && this.properties.preDatas) {
            console.log("predata", this.title, this.properties.preDatas);
            return this.properties.preDatas;
        }
        const func: any = isMock ? this.getInputMockDataDetailsNot0 : this.getInputDataDetailsNot0;
        const detail = func.call(this);
        datas = detail.datas;
        inputInfos = detail.inputInfos;
        console.log("getPre", this.title, detail);
        const outputInfos = this.getOutputInfosNot0();
        return this.cpcRunCode(datas, inputInfos, outputInfos);
    }

    public getPreNode(slot: number): CpcBase | undefined {
        const inputInfo = this.getInputInfo(slot);
        if (inputInfo) {
            if (inputInfo.link) {
                // 没看到link中id为0的情况，暂时忽略
                const graph: any = this.graph;
                if (graph.links) {
                    // eslint-disable-next-line no-restricted-syntax
                    for (const i in graph.links) {
                        const link = graph.links[i];
                        if (link.id === inputInfo.link) {
                            return graph.getNodeById(link.origin_id);
                        }
                    }
                }
            }
        }
        return undefined;
    }

    /**
     * mock 输入节点的详细信息，排除了0
     */
    public getInputMockDataDetailsNot0() {
        const inputInfos = [];
        const datas = [];
        for (let i = 1; i < this.inputs.length; i++) {
            const info: any = this.getInputInfo(i);
            let data;
            const preNode = this.getPreNode(i);
            if (preNode) {
                const preMock = preNode.cpcRun(true);
                for (let j = 1; i < preNode.outputs.length; j++) {
                    const output = preNode.outputs[j];
                    if (output.links && output.links.length > 0) {
                        if (output.links.indexOf(info.link) !== -1) {
                            data = preMock[j - 1];
                            break;
                        }
                    }
                }
            }
            inputInfos.push({
                type: info?.type, // 字段类型
                name: info?.name, // 字段名称
                islink: this.isInputConnected(i), // 是否链接输入节点
            });
            datas.push(data);
        }
        return { datas, inputInfos };
    }
}
